package proj.reseau;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;

public class Client extends TimerTask{

	Config config;
	public Fichiers fichiers;
	Socket trackerSock=null;
	List<Pair<String, Integer>> clients;
	List<BufferMap> bufferMapClients;
	BufferedReader in = null;
	OutputStreamWriter out = null;
	private Server server=null;

	public Socket newTrackerSock()
	{
		try {
			if(trackerSock!=null)
			{
				trackerSock.close();
			}
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		try {
			trackerSock = new Socket(config.getProperty("tracker-ip"),
					Integer.parseInt(config.getProperty("listening-port")));

		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		if (trackerSock == null) {
			System.err.println("erreur de connexion au tracker");
		}

		try {
			in = new BufferedReader(new InputStreamReader(
					trackerSock.getInputStream()));
			out = new OutputStreamWriter(trackerSock.getOutputStream());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (in == null || out == null) {
			System.err.println("erreur de lecture/ecriture dans la socket");
		}
		return trackerSock;
	}
	/**
	 * parsage a faire
	 * @param FileNameToLookFor
	 * @return
	 */
	public String look(String FileNameToLookFor)
	{
		newTrackerSock();
		String response=null;
		String msg = "look [filename=\"" + FileNameToLookFor + "\"]";

		if (in == null || out == null) {
			System.err.println("erreur de lecture/ecriture dans la socket");
		}
		try {
			out.write(msg);
			System.err.println("requète envoyée: " + msg);
			out.flush();
			response = in.readLine();
			System.err.println("reponse du look : "+response);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return response;
	}

	public void download(String fileName)
	{
		//newTrackerSock();
		String response=look(fileName);
		//System.err.println("la string reçue est :"+response);
		if(!response.trim().equals("list []"))
		{
			response = response.substring(response.indexOf("[")+1, response.indexOf("]"));
			String fileNameReceived = response.substring(0, response.indexOf(' '));
			if(!fileNameReceived.equals(fileName))
			{
				System.err.println("pas de fichier de nom "+fileName+" trouvé");
				return;
			}
			response = response.substring(response.indexOf(" ")+1);
			int size = Integer.parseInt(response.substring(0, response.indexOf(" ")));
			response = response.substring(response.indexOf(" ")+1);
			int pieceSize = Integer.parseInt(response.substring(0, response.indexOf(" ")));
			//System.out.println("response vaut :"+response);
			response = response.substring(response.indexOf(" ")+1);
			String key = response;
			//System.out.println("la key est :"+key);
			Fichier fichier = new Fichier(config.getProperty("leeches-directory")+'/'+fileName, fileName, size, key, pieceSize);
			fichiers.addLeech(fichier);
			//System.out.println("fichier a dl :"+file.toString());
			//pour le test a modifier
			List<Pair<String, Integer>> fileList=getfile(key);
			//fileList.add(new Pair<String, Integer>("ssh.enseirb.fr", Integer.valueOf("60021")));

			for(Pair<String, Integer> p : fileList)
			{
				clients.add(p);
				bufferMapClients.add(interested(fichier, p));

			}

			boolean downloading = true;

			List<Integer> clientCounter = new ArrayList<Integer>();
			for(int i=0;i<clients.size();i++)
			{
				clientCounter.add(0);
			}
			int index=-1;
			List<List<Integer>> piecesToAskFor= new ArrayList<List<Integer>>();//on stock ici les parties a demander à chaque client
			int maxPiecesPerConnexion=Integer.parseInt(config.getProperty("maximum-pieces-per-connexion"));
			int indexesDownloaded=0;
			int nbrOfPieces=(int) Math.floor(((double)fichier.getTaille())/(double)fichier.getPiece_size())+1;
			//System.err.println("taille lllllllllllllllllllllllllloooooooooooolllllllllllllllllll "+nbrOfPieces);
			while(downloading)
			{
				piecesToAskFor.add(new ArrayList<Integer>());
				index=fichier.getBufferMap().getNeededIndex(index+1);
				if(index==-1)
				{
					for(int i=0;i<clients.size();i++)
					{
						getPieces(clients.get(i) , key, fichier.getPiece_size(), piecesToAskFor.get(i));
					}
					downloading=false;

					index=fichier.getBufferMap().getNeededIndex(0);
					if(index==-1)
					{
						System.out.println("impossible de télécharger le fichier");
					}
				}
				else
				{

					int clientIndex=getValidClientIndex(index, clientCounter, bufferMapClients);
					System.out.println("on demande au client "+clientIndex);
					if(clientIndex!=-1)
					{
						piecesToAskFor.get(clientIndex).add(index);
						if(piecesToAskFor.get(clientIndex).size()>=maxPiecesPerConnexion)
						{
							//System.out.println("on a 5 pieces a demander");
							getPieces(clients.get(clientIndex) , key, fichier.getPiece_size(), piecesToAskFor.get(clientIndex));
							piecesToAskFor.get(clientIndex).clear();
							indexesDownloaded++;
						}
					}
					else
					{
						System.out.println("une partie du fichier est indisponible sur le réseau, le téléchargement est arrêté");
						downloading=false;
					}
					if(indexesDownloaded>=nbrOfPieces)
					{
						for(int i=0;i<clients.size();i++)
						{
							getPieces(clients.get(i) , key, fichier.getPiece_size(), piecesToAskFor.get(i));
						}
						downloading=false;
					}

				}
			}
			System.out.println("le fichier est fini de télécharger");

		}
		else
		{
			System.err.println("Le fichier recherché n'est pas sur le reseau");
		}
	}

	public void getPieces(Pair<String, Integer> p, String key, int pieceSize, List<Integer> indexes)
	{
		//System.out.println("un getpieces est demandé");
		Socket sock;
		//System.out.println("on creer la socket avec l'addr:"+p.getFirst()+" et le port: "+p.getSecond());


		String msg = "getpieces " + key + " [";
		for(int i=0;i<indexes.size()-1;i++)
		{
			msg=msg.concat(indexes.get(i).toString() + " ");
		}
		msg=msg.concat(indexes.get(indexes.size()-1)+"]\n");

		System.out.println("un getpieces est demandé : "+msg+" au client "+p.getFirst()+":"+p.getSecond());
		try {
			sock = new Socket(p.getFirst(),p.getSecond());
			try {
				in = new BufferedReader(new InputStreamReader(
						sock.getInputStream()));
				out = new OutputStreamWriter(sock.getOutputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			out.write(msg);
			out.flush();
			int indexsize = 0;
			for(Integer i : indexes)
			{
				String temp = new String(i.toString());
				indexsize += temp.length();
			}
			//System.out.println("index size: " + indexsize);
			int size=0;
			if(indexes.get(indexes.size()-1) == (int) Math.floor(((double)fichiers.getFichier(key).getTaille())/(double)fichiers.getFichier(key).getPiece_size()))
			{
				int size2=(int) (fichiers.getFichier(key).getTaille()%fichiers.getFichier(key).getPiece_size());
				size=39+(1+pieceSize)*(indexes.size()-1)+indexsize+5+size2;
			}
			else
			{
				size=39+(1+pieceSize)*(indexes.size())+indexsize+5;
			}
			char[] response = new char[size];
			in.read(response,0,size);

			File f = new File(fichiers.getFichier(key).getPath());
			if(f.createNewFile())
				System.out.println("Le fichier "+fichiers.getFichier(key).getFileName()+" a été créé");
			RandomAccessFile raf = new RandomAccessFile(fichiers.getFichier(key).getPath(), "rw");
			raf.setLength(fichiers.getFichier(key).getTaille());

			String data = new String(response);

			System.out.println("data reçu : "+data);
			System.out.println("taille de data :"+data.length());
			data = data.substring(39, size);
			System.err.println("les data sont : "+data);
			System.out.println("   index size= "+indexes.size());
			for(int i=0; i < indexes.size(); i++)
			{
				System.out.println("on est a la piece "+i);
				int piecenumber = Integer.parseInt(data.substring(0, data.indexOf(':')));
				raf.seek(piecenumber*pieceSize);
				String dataToWrite;
				if(i == indexes.size()-1)
				{
					dataToWrite = data.substring(data.indexOf(':')+1);
					dataToWrite = dataToWrite.substring(0, dataToWrite.indexOf("]"));
				}
				else
				{
					dataToWrite = data.substring(data.indexOf(':')+1, data.indexOf(':')+pieceSize+1);
				}
				System.err.println("Data to write : "+dataToWrite);
				raf.writeChars(dataToWrite);
				System.out.println("data avant substr : "+data);
				//System.out.println("data - 2048 chars : "+data.substring(2048));
				if(i != indexes.size()-1)
				{
					data = data.substring(data.indexOf(':')+pieceSize+2);
				}
				System.out.println("data apres substr : "+data);
			}

			raf.close();
			sock.close();
		}catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	private int getValidClientIndex(int pieceIndex, List<Integer> clientCounter, List<BufferMap> bufferMapClients)
	{
		int counterMin=clientCounter.get(0);
		int minClientId=0;
		//recherche le client qui a la partie de fichier et qui a le plus petit clientCounter
		for(int i=0;i<clientCounter.size();i++)
		{
			if(clientCounter.get(i)<counterMin && bufferMapClients.get(i).havePiece(pieceIndex))
			{
				counterMin=clientCounter.get(i);
				minClientId=i;
			}
		}
		//System.out.println("l'id du client est "+ minClientId);
		if(bufferMapClients.get(minClientId).havePiece(pieceIndex))
		{
			int count=clientCounter.get(minClientId);
			clientCounter.set(minClientId, count+1);
			return minClientId;
		}
		return -1;
	}


	public BufferMap interested(Fichier f, Pair<String, Integer> p)
	{
		String response=null;
		String msg = "interested " + f.getKey()+"\n";
		BufferMap bf=null;
		Socket sock;
		try {
			sock = new Socket(p.getFirst(),p.getSecond());
			try {
				in = new BufferedReader(new InputStreamReader(
						sock.getInputStream()));
				out = new OutputStreamWriter(sock.getOutputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			out.write(msg);
			out.flush();

			System.err.println("requête envoyée: " + msg);
			int tailleresponse = 4+32+1+1+(int)Math.floor(((double)f.getTaille())/(double)f.getPiece_size())+1;
			int size=(int)Math.floor(((double)f.getTaille())/(double)f.getPiece_size())+1;
			char[] str = new char[tailleresponse];
			in.read(str, 0, tailleresponse);
			response = new String(str);
			System.err.println("réponse du interested : "+response);

			String key=response.substring(response.indexOf(" ", 0), response.indexOf(" ", 10));
			String bufferMap=response.substring(response.indexOf(" ", 10), response.indexOf(" ", 10)+size+1);
			System.err.println("taille "+size);
			bf=new BufferMap(size);
			bf.setBufferMap(bufferMap);
			sock.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return bf;
	}


	public List<Pair<String, Integer>> getfile(String key)
	{
		newTrackerSock();
		String msg = "getfile " + key;
		List<Pair<String, Integer>> l = new ArrayList<Pair<String, Integer>>();
		try {
			System.err.println("requête envoyée: "+msg);
			out.write(msg);
			out.flush();
			//System.err.println("requète envoyée: " + msg);
			String response = in.readLine();
			System.err.println("réponse du getfile: "+response);
			response = response.substring(response.indexOf("[")+1, response.indexOf("]"));
			//System.out.println("la reponse est:"+response);
			String[] tokens = response.split(" ");
			for(String s : tokens)
			{
				//System.out.println("le token a parser est :"+s+"\n");
				String ip = s.substring(0, s.indexOf(":"));
				String port = s.substring(s.indexOf(":")+1);

				l.add(new Pair<String, Integer>(ip, Integer.valueOf(port)));
			}
		}catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return l;
	}


	public void announceClientToTracker(){
		newTrackerSock();
		String msg = "announce listen " + config.getProperty("listening-port")
		+ " seed [" + fichiers.getFormatedSeedsList() + "] leech ["
		+ fichiers.getFormatedLeechesKeyList() + "]";
		try {
			out.write(msg);
			out.flush();
			System.err.println("requête envoyée: " + msg);
			String response = in.readLine();
			System.err.println("réponse du announce "+response);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void updateClientToTracker() {
		newTrackerSock();
		String msg = "update " + "seed [" + fichiers.getFormatedSeedsKeyList() + "] leech ["
		+ fichiers.getFormatedLeechesKeyList() + "]";
		try {
			out.write(msg);
			out.flush();
			System.err.println("requête envoyée: " + msg);
			String response = in.readLine();
			System.err.println("réponse du update: "+response);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public Client() {
		this.config = new Config();
		System.err.println(config.getProperty("tracker-ip"));
		fichiers = new Fichiers(config);
		System.err.println(fichiers.toString());
		clients=new ArrayList<Pair<String, Integer>>();
		bufferMapClients=new ArrayList<BufferMap>();
		//newTrackerSock();
		server = new Server(config, fichiers);
		server.start();
		/* creer threads */
	}
	

	protected void finalize () {
	        	server.stopSever();
	        }
	
	@Override
	public void run() {
		this.updateClientToTracker();

	}

}
